//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#define __NO_LINKNODE_CONSTRUCTOR
#include <elastos.h>
#include "locmod.h"
#include <elacs.h>

#ifdef _zener
#include <usyscall.h>
#else
#include <pseudo.h>
#endif //_zener

EXTERN_C CARAPI _CarDllGetClassObject(REMuid, REIID, PInterface *);

DLinkNode g_LocModList = { &g_LocModList, &g_LocModList };
CriticalSection g_LocModListLock;

INLINE _ELASTOS Boolean IsElastosUunm(const wchar_t *pUunm)
{
    return !wcscmp(L"elastos.dll", pUunm);
}

ECode AcquireClassObjectFromLocalModule(
    /* [in] */ RClassID rclsid,
    /* [out] */ PInterface *ppObject)
{
    const wchar_t *pwszUunm = rclsid.pUunm;
    PDLLGETCLASSOBJECT pDllGetClassObject;

    assert(pwszUunm);

    ECode ec = NOERROR;
    IModule *pIModule = NULL;
    LocalModule *pLocalModule;

    Void * pContext = _Elastos_CriticalSection_Enter(&g_LocModListLock);
    pLocalModule = (LocalModule *)(g_LocModList.m_pNext);
    while ((DLinkNode *)pLocalModule != &g_LocModList) {
        if (IsEqualUunm(pLocalModule->m_pwszUunm, pwszUunm)) {
            ec = (*pLocalModule->m_pDllGetClassObject)(rclsid.clsid,
                            EIID_IClassObject, ppObject);
            pLocalModule->m_nAskCount = 0;
            _Elastos_CriticalSection_Leave(&g_LocModListLock, pContext);
            return ec;
        }
        pLocalModule = (LocalModule *)pLocalModule->m_pNext;
    }
    _Elastos_CriticalSection_Leave(&g_LocModListLock, pContext);

    if (IsElastosUunm(pwszUunm)) {
        return _CarDllGetClassObject(rclsid.clsid, EIID_IClassObject,
                        ppObject);
    }

    pLocalModule  = NULL;
    ec = _CModule_Load(pwszUunm, 0, &pIModule);
    if (FAILED(ec)) {
        goto ErrorExit;
    }

    // TODO(mips): wchar_t
#ifndef _GNUC_MIPS_KERNEL_
    ec = pIModule->GetProcAddressByName(
        "DllGetClassObject", (Address *)&pDllGetClassObject);
#else
    ec = pIModule->GetProcAddressByName(
        "D\0l\0l\0G\0e\0t\0C\0l\0a\0s\0s\0O\0b\0j\0e\0c\0t\0",
        (Address *)&pDllGetClassObject);
#endif
    if (FAILED(ec)) {
        goto ErrorExit;
    }

    pLocalModule = new LocalModule;
    if (!pLocalModule) {
        ec = E_OUT_OF_MEMORY;
        goto ErrorExit;
    }

    pLocalModule->m_pwszUunm = new wchar_t[wcslen(pwszUunm) + 1];
    if (!pLocalModule->m_pwszUunm) {
        ec = E_OUT_OF_MEMORY;
        goto ErrorExit;
    }
    wcscpy(pLocalModule->m_pwszUunm, pwszUunm);

    pLocalModule->m_pIModule = pIModule;
    pLocalModule->m_pDllGetClassObject = pDllGetClassObject;
    pLocalModule->m_nAskCount = 0;

    //TODO(mips): wchar_t
#ifndef _GNUC_MIPS_KERNEL_
    ec = pIModule->GetProcAddressByName("DllCanUnloadNow",
        (Address *)(&pLocalModule->m_pDllCanUnloadNow));
#else
    ec = pIModule->GetProcAddressByName(
                        "D\0l\0l\0C\0a\0n\0U\0n\0l\0o\0a\0d\0N\0o\0w\0",
                        (Address *)(&pLocalModule->m_pDllCanUnloadNow));
#endif
    if (FAILED(ec)) {
        pLocalModule->m_pDllCanUnloadNow = NULL;
    }

#if !defined(_wince) && !defined(_linux)
    ec = (*pDllGetClassObject)(ECLSID_ClassInfo,
            EIID_IInterface, (PInterface *)&pLocalModule->m_pClassInfo);
    if (FAILED(ec)) {
        pLocalModule->m_pClassInfo = NULL;
    }
    else {
#ifndef _win32
        ec = SysRegisterClassInfo(pLocalModule->m_pClassInfo);
#else
        ec = CPseudoSyscall::SysRegisterClassInfo(pLocalModule->m_pClassInfo);
#endif // !_win32
    }
    if (FAILED(ec)) goto ErrorExit;
#endif // !_wince

    ec = (*pDllGetClassObject)(rclsid.clsid, EIID_IClassObject, ppObject);

    if (FAILED(ec)) goto ErrorExit;

    pContext = _Elastos_CriticalSection_Enter(&g_LocModListLock);
    g_LocModList.InsertFirst(pLocalModule);
    _Elastos_CriticalSection_Leave(&g_LocModListLock, pContext);
    return ec;

ErrorExit:
    if (pIModule) {
        pIModule->Release();
    }
    if (pLocalModule) {
        if (pLocalModule->m_pwszUunm) {
            delete pLocalModule->m_pwszUunm;
        }
        delete pLocalModule;
    }
    return ec;
}

#ifdef _win32
// BUGBUG: Detach and delete the local module object from bottom ("CModule"),
// and this problem can be resolved by implmenting our own PE loader,
// but now all we can do is to avoid it.
extern void _DeleteLocalModule(IModule *pModule)
{
    LocalModule * pLocModule = NULL;

    Void * pContext = _Elastos_CriticalSection_Enter(&g_LocModListLock);
    ForEachDLinkNode(LocalModule* , pLocModule, &g_LocModList)
    {
         if (pLocModule->m_pIModule == pModule) {
              pLocModule->Detach();
              delete pLocModule->m_pwszUunm;
              delete pLocModule;
              pLocModule = NULL;
              break;
         }
    }
    _Elastos_CriticalSection_Leave(&g_LocModListLock, pContext);
}
#endif //_win32

ELAPI_(_ELASTOS Boolean) _CModule_CanUnloadAllModules()
{
    LocalModule *pLocalModule;

    Void * pContext = _Elastos_CriticalSection_Enter(&g_LocModListLock);
    pLocalModule = (LocalModule *)(g_LocModList.m_pNext);

    while ((DLinkNode *)pLocalModule != &g_LocModList) {
        if (NULL != pLocalModule->m_pDllCanUnloadNow) {
            if (NOERROR != (*pLocalModule->m_pDllCanUnloadNow)()) {
                _Elastos_CriticalSection_Leave(&g_LocModListLock, pContext);
                return FALSE;
            }
        }
        pLocalModule = (LocalModule *)pLocalModule->m_pNext;
    }

    _Elastos_CriticalSection_Leave(&g_LocModListLock, pContext);
    return TRUE;
}

ELAPI _CProcess_FreeUnusedModule(WString wszDllName)
{
    ECode ec = NOERROR;
    LocalModule *pLocalModule;
    Int16 nLen1 = 0;
    Int16 nLen2 = 0;

    if (wszDllName.IsEmpty()) return E_INVALID_ARGUMENT;

    nLen2 = wcslen(wszDllName);

    Void * pContext = _Elastos_CriticalSection_Enter(&g_LocModListLock);
    pLocalModule = (LocalModule *)(g_LocModList.m_pNext);

    while (pLocalModule != (LocalModule *)&g_LocModList) {
        LocalModule *pnext = (LocalModule *)(pLocalModule->Next());
        assert(pLocalModule->m_pwszUunm);
        nLen1 = wcslen(pLocalModule->m_pwszUunm);

        if (nLen1 < nLen2) {
            pLocalModule = pnext;
            continue;
        }
        else if (wszDllName.Compare(
                    pLocalModule->m_pwszUunm+nLen1-nLen2, StringCase_Insensitive)) {
            pLocalModule = pnext;
            continue;
        }
        else if (nLen1 != nLen2 &&
                            pLocalModule->m_pwszUunm[nLen1-nLen2] != L'/') {
            pLocalModule = pnext;
            continue;
        }
        else if (NULL == pLocalModule->m_pDllCanUnloadNow) {
            ec = E_NOT_CAR_DLL;
        }
        else if (NOERROR == (*pLocalModule->m_pDllCanUnloadNow)()) {
            pLocalModule->Detach();
            pLocalModule->m_pIModule->Release();
            delete pLocalModule->m_pwszUunm;
            delete pLocalModule;
            pLocalModule = NULL;
            ec = NOERROR;
        }
        else {
            ec = E_DLL_CANNOT_UNLOAD_NOW;
        }
        _Elastos_CriticalSection_Leave(&g_LocModListLock, pContext);
        return ec;
    }

    _Elastos_CriticalSection_Leave(&g_LocModListLock, pContext);

    return E_DLL_NOT_LOADED;
}

ELAPI_(void) _CProcess_FreeUnusedModules(Boolean bImmediate)
{
    LocalModule *pLocalModule;

    Void * pContext = _Elastos_CriticalSection_Enter(&g_LocModListLock);
    pLocalModule = (LocalModule *)(g_LocModList.m_pNext);

    while (pLocalModule != (LocalModule *)&g_LocModList) {
        LocalModule *pnext = (LocalModule *)(pLocalModule->Next());
        if (NULL != pLocalModule->m_pDllCanUnloadNow) {
            if (NOERROR == (*pLocalModule->m_pDllCanUnloadNow)()) {
                ++pLocalModule->m_nAskCount;
                /*
                * If DllCanUnloadNow() return TRUE more than or
                * equal to 3 successive times, then release the DLL.
                * If caller specify 'bImmediate' is TRUE, then release
                * the DLL immediately.
                */
                if (bImmediate || pLocalModule->m_nAskCount > 2) {
                    pLocalModule->Detach();
                    pLocalModule->m_pIModule->Release();
                    delete pLocalModule->m_pwszUunm;
                    delete pLocalModule;
                    pLocalModule = NULL;
                }
            }
            else
                pLocalModule->m_nAskCount = 0;
        }
        pLocalModule = pnext;
    }

    _Elastos_CriticalSection_Leave(&g_LocModListLock, pContext);
}

void FreeCurProcComponents()
{
    LocalModule *pCurModule = NULL;

    Void * pContext = _Elastos_CriticalSection_Enter(&g_LocModListLock);
    pCurModule = (LocalModule *)(g_LocModList.m_pNext);

    while (pCurModule != (LocalModule *)&g_LocModList) {
        LocalModule *pnext = (LocalModule *)(pCurModule->Next());
        ECode ecCur = S_FALSE;
        if (NULL != pCurModule->m_pDllCanUnloadNow) {
            ecCur = (*pCurModule->m_pDllCanUnloadNow)();
            if (NOERROR == ecCur) {
                pCurModule->Detach();
                pCurModule->m_pIModule->Release();
                delete pCurModule->m_pwszUunm;
                delete pCurModule;
                pCurModule = NULL;
            }
        }

        if (S_FALSE != ecCur) {
            pCurModule = (LocalModule *)(g_LocModList.m_pNext);
        }
        else {
            pCurModule = pnext;
        }
    }

    _Elastos_CriticalSection_Leave(&g_LocModListLock, pContext);
}
